package com.J.Plane;

import java.util.Vector;

import javax.microedition.khronos.opengles.GL10;

import junit.framework.Assert;

public class SceneNode {

	private static final int GENERATE_NEW_ID = -1;
	private static final String GENERATE_NEW_NAME = "";

	private int mId;
	private String mName;

	private Vector3 mPosition;
	private Vector3 mScale;
	private Quaternion mOrientation;

	private Vector3 mDerivedPosition;
	private Vector3 mDerivedScale;
	private Quaternion mDerivedOrientation;

	private boolean mNeedParentUpdate;
	private boolean mNeedChildUpdate;
	private boolean mCachedTransformOutOfDate;

	private boolean mInheritOrientation;
	private boolean mInheritScale;
	private boolean mInheritPosition;

	private Matrix44 mCachedTransform;

	private SceneNode mParent;
	private Vector<SceneNode> mChildren;
	private Entity mEntity;

	private static int sSceneNodeCounter = 0;
	private static final int SCENE_NODE_BASE = 1000;

	private static int genId() {
		++sSceneNodeCounter;
		return sSceneNodeCounter;
	}

	private static String nameFromId(int id) {
		return "SceneNode-" + Integer.toString(id);
	}

	public SceneNode() {
		initializeIdAndName(GENERATE_NEW_ID, GENERATE_NEW_NAME);		
		initializeInternalData();
	}
	public SceneNode(int id) {
		initializeIdAndName(id, GENERATE_NEW_NAME);
		initializeInternalData();
	}

	public SceneNode(String name) {
		initializeIdAndName(GENERATE_NEW_ID, name);
		initializeInternalData();
	}
	
	public SceneNode(Entity e) {
		this();
		this.attachEntity(e);
	}
	
	public void initializeIdAndName(int id, String name) {
		if(id == GENERATE_NEW_ID)
			id = genId();
		mId = id;
		
		if(name == GENERATE_NEW_NAME)
			name = nameFromId(mId); 
		mName = name;
	}

	public SceneNode setId(int id) {
		mId = id;
		return this;
	}

	public int getId() {
		return mId;
	}

	public SceneNode name(String name) {
		mName = name;
		return this;
	}

	public String name() {
		return mName;
	}

	private void initializeInternalData() {
		mPosition = Vector3.ZERO_VECTOR.clone();
		mScale = Vector3.UNIT_SCALE_VECTOR.clone();
		mOrientation = Quaternion.DEFAULT_ORIENTATION.clone();

		mDerivedPosition = mPosition;
		mDerivedScale = mScale;
		mDerivedOrientation = mOrientation;

		mNeedParentUpdate = false;
		mNeedChildUpdate = false;
		mCachedTransformOutOfDate = true;

		mInheritPosition = true;
		mInheritScale = true;
		mInheritOrientation = true;

		mCachedTransform = new Matrix44();
		mEntity = null;
	}

	public void setParent(SceneNode node) {
		Assert.assertNotNull("Parent node should not be NULL", node);
		mParent = node;
		needUpdate();
	}

	public SceneNode getParent() {
		return mParent;
	}

	public void removeParent() {
		if (mParent != null) {
			mParent = null;
			needUpdate();
		}
	}
	
	/**
	 * Add child node
	 * @param child
	 */
	public int addChild(SceneNode child) {
		Assert.assertNotNull(child);
		Assert.assertEquals(-1, this.findChild(child.name()));
		
		if(mChildren == null)
			mChildren = new Vector<SceneNode>();
		
		mChildren.add(child);
		child.setParent(this);
		
		return mChildren.size()-1;
	}
	
	/**
	 * Remove child from current node.
	 * @param node	Removed child.
	 * 
	 */
	public void removeChild(SceneNode node) {
		Assert.assertNotNull(node);
		int index = this.findChild(node.name());
		if(-1 == index) {
			// Not found.
			return;
		}
		
		mChildren.get(index).removeParent();
		node.removeParent();
		
		mChildren.remove(index);
	}
	
	
	/**
	 * Find child
	 * @param[in] name	Child name
	 * @return	Index of the node if found. Elsewhere -1.
	 * 
	 */
	public int findChild(String name) {
		if(null == mChildren)
			return -1;
		
		SceneNode n = null;
		int cnt = mChildren.size();
		for(int i=0;i<cnt;++i) {
			n = mChildren.get(i);
			if(n.hasSameName(name))
				return i;
		}
		
		return -1;
	}

	/**
	 * Check whether name equals or not.
	 * @param name name of scene node
	 * @return true if the name is same. Elsewhere return false.
	 */
	public boolean hasSameName(String name) {
		return name == this.mName;
	}

	private void needUpdate() {
		mNeedChildUpdate = true;
		mNeedParentUpdate = true;
		mCachedTransformOutOfDate = true;
	}

	private void setInheritOrientation(boolean inherit) {
		mInheritOrientation = inherit;
	}

	private void setInheritScale(boolean inherit) {
		mInheritScale = inherit;
	}

	private void setInheritPosition(boolean inherit) {
		mInheritPosition = inherit;
	}

	/**
	 * This guy returns full-transform matrix.
	 * 
	 * @return
	 */
	private Matrix44 getFullTransform() {
		if (mCachedTransformOutOfDate == true) {
			mCachedTransform.makeTransform(this.getDerivedPosition(), this
					.getDerivedScale(), this.getDerivedOrientation());
			mCachedTransformOutOfDate = false;
		}

		return this.mCachedTransform;
	}

	public final Vector3 getDerivedPosition() {
		if (mNeedParentUpdate)
			updateFromParent();

		return mDerivedPosition;
	}

	public final Quaternion getDerivedOrientation() {
		if (mNeedParentUpdate)
			updateFromParent();

		return mDerivedOrientation;
	}

	public final Vector3 getDerivedScale() {
		if (mNeedParentUpdate)
			updateFromParent();

		return mDerivedScale;
	}

	public void position(float x, float y, float z) {
		mPosition.set(x, y, z);
		this.needUpdate();
	}

	public final Vector3 position() {
		return mPosition;
	}

	public SceneNode scale(float v) {
		return scale(v,v,v);
	}
	public SceneNode scale(float x, float y, float z) {
		mScale.set(x, y, z);
		this.needUpdate();
		return this;
	}
	public final Vector3 scale() {
		return mScale;
	}

	public SceneNode rotate(float angleDegree,
							float x,
							float y,
							float z)
	{
		mOrientation.quaternionFromAngleAxis(Matrix44.DEG2RAD(angleDegree), x, y, z);
		needUpdate();
		return this;
	}
	
	public SceneNode translate(float x, float y, float z) {
		mPosition.add(x, y, z);
		this.needUpdate();
		return this;
	}

	public void attachEntity(Entity entity) {
		Assert.assertNull(mEntity);
		Assert.assertNotNull(entity);
		mEntity = entity;
		//mEntity.setParent(this);
	}

	public void detachEntity() {
		Assert.assertNotNull(mEntity);
		//mEntity.removeParent();
		mEntity = null;
	}

	public void updateFromParent() {
		if (mParent != null) {
			final Quaternion parentOrientation = mParent
					.getDerivedOrientation();
			if (mInheritOrientation) {
				Quaternion.multiply( mOrientation, parentOrientation,
						mDerivedOrientation);
			} else {
				mDerivedOrientation.copyFrom(this.mOrientation);
			}

			final Vector3 parentScale = mParent.getDerivedScale();
			if (mInheritScale) {
				Vector3.multiply(parentScale, mScale, mDerivedScale);
			} else {
				mDerivedScale.copyFrom(mScale);
			}

			Vector3.multiply(parentScale, mPosition, mDerivedPosition);
			parentOrientation.rotate(mDerivedPosition, mDerivedPosition);
			mDerivedPosition.add(mParent.getDerivedPosition());
		} else {
			// No parent
			mDerivedPosition.copyFrom(mPosition);
			mDerivedScale.copyFrom(mScale);
			mDerivedOrientation.copyFrom(mOrientation);
		}

		mCachedTransformOutOfDate = true;
		mNeedParentUpdate = false;
	}

	/**
	 * Render children & attached entity.
	 */
	public void render(GL10 gl) {

		// Render children first
		if (null != mChildren) {
			int len = mChildren.size();
			for (int i = 0; i < len; ++i) {
				mChildren.get(i).render(gl);
			}
		}

		if (mEntity != null) {
			Matrix44 m = getFullTransform();
			gl.glPushMatrix();
			gl.glMultMatrixf(m.getFloatBuffer());

			mEntity.render(gl);

			gl.glPopMatrix();
		}
	}
}
